#include <assert.h>

#include <Ast.h>

#include "CompileJvm.h"
#include "ClassFile.h"
#include "SymbolPool.h"

#define DBG_LOG(fmt,...)   do { printf("%s:%d <-",__FILE__,__LINE__); printf(fmt,## __VA_ARGS__); } while(0)
#define DBG_MARK           DBG_LOG("\n");

CompileJvm::CompileJvm(const char * classname)
{
  klass=ClassFile::newClassFile(classname/*outputClassName*/);
  method=klass->addDefaultMainMethod();
  //this->klass=klass;
  //this->method=method;
  this->symbols=new SymbolPool();
};

int CompileJvm::compile(AstNodeLiteral * node)
{
  u2 index=klass->getConstantPool()->addString(node->getString());
  method->assemble(OPC_ldc1,index);
  return 0;
}

int CompileJvm::compile(AstNodeStatementList * node) 
{
  std::vector<AstNode*> & nodes=node->getChildren();
  for(u2 i=0;i<nodes.size();i++) {
    AstNode * node=nodes[i];
    node->compile(this);
  }
  return 0;
}

int CompileJvm::compile(AstNodeLValue * node) 
{
  u2 ii=symbols->addSymbol(node->getSymbol())*2;
  switch(ii) {
    case 0:
      method->assemble(OPC_dstore_0);
      break;
    case 1:
      method->assemble(OPC_dstore_1);
      break;
    case 2:
      method->assemble(OPC_dstore_2);
      break;
    default:
      method->assemble(OPC_dstore,ii);
      break;
  }
  return 0;
}

int CompileJvm::compile(AstNodeAssignment * node) 
{
  AstNode *           lvalue=node->getLValue();
  AstNodeExpression * expr=node->getExpression();

  expr->compile(this);
  lvalue->compile(this);
  return 0;
}

int CompileJvm::compile(AstNodeIfStatement * node) 
{
  u4 targetFalse;
  u4 targetTrue;

  //RelocTable relocOnTrue;
  //RelocTable relocOnFalse;
  AstNodeBooleanOperator * condition=node->getCondition();
  AstNode                * true_body=node->getTrueBody();
  AstNode                * false_body=node->getFalseBody();
  
  relocOnTruePush();
  relocOnFalsePush();

  condition->compile(this);

  targetTrue=method->getAP();

  true_body->compile(this);

  targetFalse=method->getAP();
  if(false_body) {
    u4 relocOnEnd=method->getAP();
    method->assemble(OPC_goto,0);
    targetFalse=method->getAP();
    false_body->compile(this);

    method->relocAt(relocOnEnd,method->getAP());
  }


  relocOnFalseTop()->reloc(method,targetFalse);
  relocOnTrueTop()->reloc(method,targetTrue);

  relocOnFalsePop();
  relocOnTruePop();
  return 0;
}

int CompileJvm::compile(AstNodeBooleanOperator * node) 
{
  u1 oper=node->getOper();

  AstNodeBooleanOperator * left=node->getLeft();
  AstNodeBooleanOperator * rite=node->getRite();
  AstNodeCompareExpression * cmp=node->getCmp();

  if(cmp) {
    u1 opcode=cmp->getOpcodeForFalse();
    cmp->compile(this);
    relocOnFalseTop()->push_back(method->getAP());
    method->assemble(opcode,0);
  } else {
    RelocTable local_relocOnTrue;
    RelocTable local_relocOnFalse;
    u4 targetTrue;
    u4 targetFalse;
    switch(oper) {
      case AstNodeBooleanOperator ::_and:
        relocOnTruePush();
        left->compile(this);

        targetTrue=method->getAP();

        relocOnTrueTop()->reloc(method,targetTrue);
        relocOnTruePop();

        rite->compile(this);
        break;

      case AstNodeBooleanOperator ::_or:
        relocOnFalsePush();
        left->compile(this);

        relocOnTrueTop()->push_back(method->getAP());
        method->assemble(OPC_goto,0);

        targetFalse=method->getAP();
        relocOnFalseTop()->reloc(method,targetFalse);
        relocOnFalsePop();

        rite->compile(this);
        break;

      case AstNodeBooleanOperator ::_not:
        relocOnTrueFalseSwitch();
        left->compile(this);
        relocOnTrueFalseSwitch();
        relocOnFalseTop()->push_back(method->getAP());
        method->assemble(OPC_goto,0);
        break;

      default:
        assert(0);
    }
  }
  return 0;
}

int CompileJvm::compile(AstNodePrintExpression * node) 
{
  u2 constantFieldRef=klass->addFieldRef(
      "java/lang/System","out", "Ljava/io/PrintStream;");

  u2 constantMethodRef;

  if(node->getEndLine())
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","println", "(D)V");
  else
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","print", "(D)V");

  method->assemble(OPC_getstatic,constantFieldRef);
  node->getExpression()->compile(this);
  method->assemble(OPC_invokevirtual,constantMethodRef);

  return 0;
}


int CompileJvm::compile(AstNodePrintLiteral * node) 
{

  u2 constantFieldRef=klass->addFieldRef(
      "java/lang/System","out", "Ljava/io/PrintStream;");

  u2 constantMethodRef;

  if(node->getEndLine())
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","println", "(Ljava/lang/String;)V");
  else
    constantMethodRef=klass->addMethodRef(
        "java/io/PrintStream","print", "(Ljava/lang/String;)V");

  method->assemble(OPC_getstatic,constantFieldRef);
  node->getLiteral()->compile(this);
  method->assemble(OPC_invokevirtual,constantMethodRef);

  return 0;
}

int CompileJvm::compile(AstNodeCompareExpression * node) 
{
  u1 oper=node->getOper();
  AstNodeExpression * left=node->getLeft();
  AstNodeExpression * rite=node->getRite();
  switch(oper) {
    case AstNodeCompareExpression::_equal:
      //left->compile(klass,method);
      //rite->compile(klass,method);
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    case AstNodeCompareExpression::_less:
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    case AstNodeCompareExpression::_greater:
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    case AstNodeCompareExpression::_less_equal:
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    case AstNodeCompareExpression::_greater_equal:
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    case AstNodeCompareExpression::_not_equal:
      left->compile(this);
      rite->compile(this);
      method->assemble(OPC_dcmpg);
      break;
    default:
      assert(0);
  }
  return 0;
}

int CompileJvm::compile(AstNodeBinaryOperator * node) 
{
  int op=node->getOperator();
  AstNodeExpression * left=node->getLeft();
  AstNodeExpression * rite=node->getRite();

  left->compile(this);
  rite->compile(this);

  switch(op) {
    case '+':
     method->assemble(OPC_dadd);
     break;
    case '-':
     method->assemble(OPC_dsub);
     break;
    case '*':
     method->assemble(OPC_dmul);
     break;
    case '/':
     method->assemble(OPC_ddiv);
     break;
    default:
     assert(0);
  }
  return 0;
}

int CompileJvm::compile(AstNodeForStatement * node) 
{
  AstNode * startNode=node->getStartNode();
  AstNodeBooleanOperator * cond=node->getCond();
  AstNode *                nextNode=node->getNextNode();
  AstNode *                body=node->getBody();

  startNode->compile(this);

  u2 targetLoop=method->getAP();

  relocOnTruePush();
  relocOnFalsePush();

  cond->compile(this);

  //u2 targetBody=method->getAP();

  body->compile(this);

  nextNode->compile(this);
  
  u2 p2=method->getAP();
  method->assemble(OPC_goto,0);
  method->relocAt(p2, targetLoop);

  relocOnFalseTop()->reloc(method,method->getAP());

  relocOnFalsePop();
  relocOnTruePop();
  return 0;
}

int CompileJvm::compile(AstNodeDoubleConstant * node) 
{
  u2 index=klass->getConstantPool()->addDouble(node->getValue());
  method->assemble(OPC_ldc2w,index);
  return 0;
}

int CompileJvm::compile(AstNodeExpressionVariable * node) 
{
  u2 ii=symbols->addSymbol(node->getName())*2;
  switch(ii) {
    case 0:
      method->assemble(OPC_dload_0);
      break;
    case 1:
      method->assemble(OPC_dload_1);
      break;
    case 2:
      method->assemble(OPC_dload_2);
      break;
    default:
      method->assemble(OPC_dload,ii);
      break;
  }
  return 0;
}

#if 0
int CompileJvm::compile(AstNodeExpression * node) 
{
  u1 oper=node->getOper();
  AstNodeBooleanOperator * left=getLeft();
  AstNodeBooleanOperator * rite=getRite();
  switch(oper) {
    case _equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _less:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _greater:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _less_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _greater_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    case _not_equal:
      left->compile(klass,method);
      rite->compile(klass,method);
      method->assemble(OPC_dcmpg);
      break;
    default:
      assert(0);
  }
  return 0;
}
#endif

int CompileJvm::finalize()
{
  method->assemble(OPC_return);
  klass->save();
  return 0;
}
